/*
    Dominate 3D - A cube-based game of life

    Copyright (C) 2007, Craig H. Miller (kidmosey@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "sharedobject.h"
#include "option.h"
#include "client.h"
#include "util.h"
#include "cube.h"
#include "player.h"

#include <GL/glfw.h>
#include <map>

// defined in the main module.
extern SharedObject<Cube> g_Cube;
extern void UpdateStatusWindow(const std::string &str);
extern void CloseStatusWindow();

// defined in the game module
extern std::vector<std::pair<std::string, Color> > g_listChatStrings;
extern float g_ChatStartTime;

namespace ClientFunctions
{
    void clSetCell(Client &cl, const std::vector<SocketParam> &params)
    {
        int cellID   = atoi(params[0].c_str()),
                       playerID = atoi(params[1].c_str());

        Cell::ESTATE state = (Cell::ESTATE )atoi(params[2].c_str());

        Cube &cube = ::g_Cube.Lock();
        Cell *cell = cube.GetCell(cellID);
        Player *p  = cube.GetPlayer(playerID);
        if (cell)
        {
            if (state == Cell::SELECTED)
            {
                cell->Select();
                cube.m_selectedCell = cell;
            }
            else if (cube.m_selectedCell == cell)
            {
                cell->Unselect();
                cube.m_selectedCell = 0;
            }
            cell->SetState(state);

            if (p)
                cell->SetPlayer(p);
        }

        ::g_Cube.Unlock();
    }

    void clSetPlayerName(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() == 2);

        printf("CLIENT[%d]: Setting the player's name.\n", cl.GetSocket());

        int cliID = atoi(params[0].c_str());
        ::g_Cube.Lock().GetPlayer(cliID)->SetName(params[1]);
        ::g_Cube.Unlock();
    }

    void clServerSettings(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() % 2 == 0);

        printf("CLIENT[%d]: Receiving server settings... ", cl.GetSocket());

        for (unsigned int i = 0; i < params.size(); i += 2)
            SetOption(params[i], params[i+1]);

        printf("done.\n");
    }

    void clPickCell(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() == 1);

        printf("CLIENT[%d]: Selecting cell %d.\n", cl.GetSocket(), atoi(params[0].c_str()));
    }

    void clServerFull(Client &cl, const std::vector<SocketParam> &params)
    {
        //assert(params.size() == 1);

        printf("CLIENT[%d]: A game is already in progress.\n", cl.GetSocket());
        closesocket(cl.GetSocket());
    }

    void clSetCellState(Client &cl, const std::vector<SocketParam> &params)
    {
        //assert(params.size() == 1);

        printf("CLIENT[%d]: Setting cell state.\n", cl.GetSocket());
    }

    void clStartGame(Client &cl, const std::vector<SocketParam> &params)
    {
        //assert(params.size() == 1);

        printf("CLIENT[%d]: Starting the game.\n", cl.GetSocket());
        //DoGameLoop();
    }

    void clSetCurrentPlayer(Client &cl, const std::vector<SocketParam> &params)
    {
        if (params.size() != 1) return;

        g_Cube.Lock().SetCurrentPlayer(atoi(params[0].c_str()));
        g_Cube.Unlock();
    }

    void clRemovePlayer(Client &cl, const std::vector<SocketParam> &params)
    {
        if (params.size() != 1) return;

        ::g_Cube.Lock().RemovePlayer(atoi(params[0].c_str()));
        ::g_Cube.Unlock();
    }

    void clSetPlayerScore(Client &cl, const std::vector<SocketParam> &params)
    {
        if (params.size() != 5) return;

        Player *p = ::g_Cube.Lock().GetPlayer(atoi(params[0].c_str()));

        if (p)
        {
            p->m_taken     = atoi(params[1].c_str());
            p->m_total     = atoi(params[2].c_str());
            p->m_numTurns  = atoi(params[3].c_str());
            p->m_totalTime = atof(params[4].c_str());
        }

        ::g_Cube.Unlock();
    }

    void clAddPlayer(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() == 6);

        printf("CLIENT[%d]: Creating the player.\n", cl.GetSocket());

        int cliID = atoi(params[0].c_str());

        std::string name = params[1];
        Color c(
            (float )atof(params[2].c_str()),
            (float )atof(params[3].c_str()),
            (float )atof(params[4].c_str()),
            (float )atof(params[5].c_str()));
        Player *p = new Player(name, c);
        p->SetID(cliID);
/*
        if (cl.GetID() == cliID)
        {
            cl.SetPlayer(p);
        }
*/
        ::g_Cube.Lock().AddPlayer(p);
        ::g_Cube.Unlock();
    }

    void csChatText(Client &cl, const std::vector<SocketParam> &params)
    {
        if (params.size() != 2) return;

        Player *p = ::g_Cube.Lock().GetPlayer(atoi(params[0].c_str()));
        ::g_Cube.Unlock();

        if (p)
        {
            std::string msg = "<" + p->GetName() + "> " + params[1];
            g_listChatStrings.push_back(std::pair<std::string, Color>(msg, p->c));
            g_ChatStartTime = glfwGetTime();
        }
    }

    void csDisconnect(Client &cl, const std::vector<SocketParam> &params)
    {
    }

#define D(id, func) std::pair<TMessage, Client::MessageFunc>(id, func)
    const std::pair<TMessage, Client::MessageFunc> MESSAGEFUNC_PAIRS[] =
    {
        D(CL_SERVERSETTINGS, clServerSettings),
        D(CL_PICKCELL, clPickCell),
        D(CL_SERVER_FULL, clServerFull),
        D(CL_SET_CELL_STATE, clSetCellState),
        D(CL_START_GAME, clStartGame),
        D(CL_ADD_PLAYER, clAddPlayer),
        D(CL_SET_PLAYER_NAME, clSetPlayerName),
        D(CL_SETCELL, clSetCell),
        D(CL_SET_CURRENT_PLAYER, clSetCurrentPlayer),
        D(CL_SET_PLAYER_SCORE, clSetPlayerScore),
        D(CL_REMOVE_PLAYER, clRemovePlayer),
        D(CS_CHATTEXT, csChatText),
        D(CS_DISCONNECT, csDisconnect),
        //D(CL_CURRENTSELECTION, clCurrentSelection)
    };
    std::map<TMessage, Client::MessageFunc> MESSAGEFUNC(MESSAGEFUNC_PAIRS, MESSAGEFUNC_PAIRS + COUNT(MESSAGEFUNC_PAIRS));
#undef D
}

// connect to a server (for the client interface)
Client::Client(): m_sock(INVALID_SOCKET), m_id(-1)
{
}

void Client::RegisterPlayer()
{
    // wait for our id from the server.
    u_long id;
    recv(m_sock, (char *)&id, sizeof(u_long), 0);
    m_id = ntohl(id);

    // send a request for server settings
    SendMessage(SV_GETSERVERSETTINGS, std::vector<SocketParam>());

    // wait for the server settings to get sent back
    ReadMessage(ClientFunctions::MESSAGEFUNC);

    // register our player on the server
    SocketParam params[] =
    {
        GetOption("PlayerName")
    };
    SendMessage(SV_REGISTER_PLAYER, std::vector<SocketParam>(params, params+COUNT(params)));
}

void Client::RegisterBot(const std::string &name)
{
    // wait for our id from the server.
    u_long id;
    recv(m_sock, (char *)&id, sizeof(u_long), 0);
    m_id = ntohl(id);

    // register our player on the server
    SocketParam params[] = { name };
    SendMessage(SV_REGISTER_PLAYER, std::vector<SocketParam>(params, params+COUNT(params)));
}

/*
Client::Client(const char addr[], int port, void (*ProgressFunc)(const std::string &))
        : m_sock(INVALID_SOCKET), m_id(-1)
{
    // connect to the server.
    if (ProgressFunc) ProgressFunc("Connecting to the server.");
    Connect(addr, port);

    // wait for our id from the server.
    if (ProgressFunc) ProgressFunc("Retrieving client info from the server.");
    uint32_t id;
    recv(m_sock, (char *)&id, sizeof(uint32_t), 0);
    m_id = ntohl(id);

    // send a request for server settings
    if (ProgressFunc) ProgressFunc("Retrieving settings from the server.");
    SendMessage(SV_GETSERVERSETTINGS, std::vector<SocketParam>());

    // wait for the server settings to get sent back
    ReadMessage(ClientFunctions::MESSAGEFUNC);

    // register our player on the server
    if (ProgressFunc) ProgressFunc("Registering client with server.");
    SocketParam params[] =
    {
        GetOption("PlayerName")
    };
    SendMessage(SV_REGISTER_PLAYER, std::vector<SocketParam>(params, params+COUNT(params)));

    // Wait for a response to create this player
    //ReadMessage();
}
*/

SocketParam ReadParam(SOCKET sock)
{
    char c;
    recv(sock, &c, 1, 0);

    std::auto_ptr<char> buffer(new char[c+1]);
    recv(sock, buffer.get(), c, 0);
    buffer.get()[c] = 0;

    return SocketParam(buffer.get());
}

void Client::SendMessage(const TMessage type, const std::vector<SocketParam> &params)
{
    assert(m_sock != INVALID_SOCKET);

    //printf("SOCKET[%d|%d]: Sending message.\n", m_id, m_sock);
    int len = 0;

    // send the type (char)
    char c = (char )type;
    len = send(m_sock, &c, 1, 0);

    // send the param count (char)
    char numParams = (char )params.size();
    len = send(m_sock, &numParams, 1, 0);

    // send the parameters
    for (int i = 0; i < numParams; i++)
    {
        const char *buffer = params[i].c_str();

        char c = params[i].length();
        send(m_sock, &c, 1, 0);
        send(m_sock, buffer, c, 0);
    }
}

void Client::ReadMessage(std::map<TMessage, MessageFunc> &messageMap)
{
    //printf("SOCKET[%d|%d]: Recieving message.\n", m_id, m_sock);

    // read the message type (char)
    char c;
    recv(m_sock, (char *)&c, 1, 0);
    TMessage type = (TMessage )c;

    // ignore this message if we don't support it
    if (messageMap.find(type) == messageMap.end())
    {
        // purge the buffer.
        if (CheckReadStatus(m_sock) == 1)
        {
            char buffer[1024];
            recv(m_sock, buffer, sizeof(buffer), 0);
        }
        return;
    }

    // read the parameter count (char)
    char numParams;
    recv(m_sock, (char *)&numParams, 1, 0);

    // create parameters
    std::vector<SocketParam> params;
    while (numParams--)
        params.push_back(ReadParam(m_sock));

    assert(messageMap.find(type) != messageMap.end());
    messageMap[type](*this, params);
}

int Client::Connect(const char addr[], int port)
{
    struct hostent *host;
    host = gethostbyname( addr );

    if (!host)
        host = gethostbyaddr(addr, strlen(addr), AF_INET);

    if (!host)
        return SOCKET_ERROR;

    struct sockaddr_in sin;
    memset( &sin, 0, sizeof sin );

    sin.sin_family = AF_INET;
    sin.sin_port = htons( port );
    sin.sin_addr.s_addr = ((struct in_addr *)(host->h_addr))->s_addr;

    m_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

    return connect(m_sock, (struct sockaddr *)&sin, sizeof(sin));
}
